ES6中的元编程 - Proxy & Reflect

ES6中的元编程 - Proxy & Reflect

什么是元编程

元编程(Metaprogramming)是指某类计算机程序的编写,这类计算机程序编写或者操纵其他程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。很多情况下与手工编写全部代码相比工作效率更高。编写元程序的语言称之为元语言,被操作的语言称之为目标语言。一门语言同时也是自身的元语言的能力称之为反射。

简单一点来理解元编程其实就是改变源码里面的东西,对其原本的功能进行了修改,能"介入"的对象底层操作进行的过程中,并加以影响。元编程中的元概念可以理解为程序本身。"元编程能让你拥有可以扩展程序本身能力"。

Proxy(代理)

用于自定义的对象的行为,比如修改set和get,感觉是es5的Object.defineProerty()方法的es6升级版

代码示例

数据劫持,验证操作

    
let handler = {
    get: function(target, key){
        return key in target ? target[key] : 37;
    },
   set: function(target, key, value) {
    if (key === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }
// The default behavior to store the value
target[key] = value;

} }; let p = new Proxy({}, handler); p.a = 1; p.b = undefined; person.age = 100;

console.log(person.age); // 100

person.age = 'young'; // 抛出异常: Uncaught TypeError: The age is not an integer

person.age = 300; // 抛出异常: Uncaught RangeError: The age seems invalid console.log(p.a, p.b); // 1, undefined

console.log('c' in p, p.c); // false, 37

函数节流

    
        const createThrottleProxy = (fn, rate) => {
  let lastClick = Date.now() - rate;
  return new Proxy(fn, {
    apply(target, context, args) {
      if (Date.now() - lastClick >= rate) {
        fn.bind(target)(args);
        lastClick = Date.now();
      }
    }
  });
};

const handler = () => console.log('Do something...'); const handlerProxy = createThrottleProxy(handler, 1000); document.addEventListener('scroll', handlerProxy);

Proxy 实例的方法

get(target, propKey, receiver): 拦截对象属性的读取,比如proxy.foo和proxy['foo']。

set(target, propKey, value, receiver): 拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。

has(target, propKey): 拦截propKey in proxy的操作,返回一个布尔值。

deleteProperty(target, propKey): 拦截delete proxy[propKey]的操作,返回一个布尔值。

ownKeys(target): 拦截Object.getOwnPropertyNames(proxy)、 Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。

getOwnPropertyDescriptor(target, propKey): 拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。

defineProperty(target, propKey, propDesc): 拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。

preventExtensions(target): 拦截Object.preventExtensions(proxy),返回一个布尔值。 getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。

isExtensible(target): 拦截Object.isExtensible(proxy),返回一个布尔值。

setPrototypeOf(target, proto): 拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。

apply(target, object, args): 拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。

construct(target, args): 拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。


Reflect(反射)

用于替代直接调用Object的方法,它并不是一个函数对象,并没有constructor,所以不用new操作符来实例化。

Reflect 有助于将默认操作从处理程序转发到目标。 以 Reflect.has() 为例,你可以将 in 运算符作为函数:

Reflect.has(Object, "assign"); // true

更好的 apply 函数

ES5 中,我们通常使用 Function.prototype.apply() 方法调用一个具有给定 this 值和 arguments 数组(或类数组对象)的函数。

Function.prototype.apply.call(Math.floor, undefined, [1.75]);

使用 Reflect.apply,这变得不那么冗长和容易理解:

Reflect.apply(Math.floor, undefined, [1.75]); // 1;

Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]); // "hello"

Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index; // 4

Reflect.apply(''.charAt, 'ponies', [3]); // "i"

检查属性定义是否成功

使用 Object.defineProperty,如果成功返回一个对象,否则抛出一个 TypeError,你将使用 try...catch 块来捕获定义属性时发生的任何错误。因为 Reflect.defineProperty 返回一个布尔值表示的成功状态,你可以在这里使用 if...else 块:

    
if (Reflect.defineProperty(target, property,    attributes)) {
  // success
} else {
  // failure
}